package tum0r.webengine.database.sql

import tum0r.webengine.extension.infoLog
import tum0r.webengine.extension.isPrimitiveType
import tum0r.webengine.extension.selectObjects
import tum0r.webengine.extension.selectValues
import tum0r.webengine.models.database.DatabaseConnect
import tum0r.webengine.models.database.PageModel
import java.sql.Connection
import java.sql.PreparedStatement

/**
 * 说明: 数据库操作
 *
 * 创建者: tum0r
 *
 * 时间: 2021/8/19 11:58
 *
 * @param[info] 数据库连接信息
 */
abstract class SQL(val info: DatabaseConnect) {
    /**
     * 数据库连接，子类里实例化和使用
     */
    var connection: Connection? = null

    /**
     * 执行SQL语句，子类里实例化和使用
     */
    var statement: PreparedStatement? = null

    /**
     * 启动数据库连接，子类实现
     *
     * @return 启动是否成功
     */
    abstract fun open(host: String, port: Int, databaseName: String, username: String, password: String, parameters: String): Boolean

    /**
     * 启动数据库连接
     *
     * @return 启动是否成功
     */
    fun open(): Boolean {
        Class.forName(this.info.type.driver)
        return open(this.info.host, this.info.port, this.info.databaseName, this.info.username, this.info.password, this.info.type.parameters)
    }

    /**
     * 数据库连接是否启动
     */
    fun isOpen(): Boolean = connection != null && !connection!!.isClosed

    /**
     * 关闭数据库连接
     */
    fun close(): Boolean {
        if (statement != null && !statement!!.isClosed) statement!!.close()
        if (isOpen()) connection!!.close()
        return true
    }

    /**
     * 执行命令并清空命令字符串
     *
     * @param[command] 命令
     */
    protected fun operationCommand(command: StringBuilder, op: String) {
        if (command.isBlank()) return
        op.infoLog()
        update(command.toString())
        command.clear()
    }

    /**
     * 初始化数据库
     *
     * @param[databaseName] 数据库名
     */
    abstract fun initDatabase(databaseName: String)

    /**
     * 初始化数据库
     */
    fun initDatabase() = initDatabase("")

    /**
     * 设置SQL语句参数
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     */
    private fun setParameters(command: String, vararg parameters: Any?): Boolean {
        if (!this.isOpen() || command.isBlank()) return false
        val regex = Regex("(@[p|P](\\d+))")
        val sequence = regex.findAll(command).toList()
        val count = sequence.count()

        this.statement = this.connection!!.prepareStatement(regex.replace(command, "?"))
        if (count > 0) for (i in 0 until count) {
            val value = parameters[sequence[i].groupValues[2].toInt()]
            this.statement!!.setObject(i + 1, if (value == null || !value::class.java.isEnum) value else (value as Enum<*>).ordinal + 1)
        }
        return true
    }

    // region Kotlin中调用
    /**
     * 查询数据库，并以对象集合的形式返回数据
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     */
    inline fun <reified T> selectObjects(command: String, vararg parameters: Any?): ArrayList<T> = selectObjects(T::class.java, command, *parameters)

    /**
     * 查询数据库，并以对象的形式返回数据
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     *
     * @return 未查询到返回null，查询结果有多条则返回第一条
     */
    inline fun <reified T> selectObject(command: String, vararg parameters: Any?): T? = selectObject(T::class.java, command, *parameters)

    /**
     * 不安全的查询数据库，并以对象集合的形式返回数据
     *
     * @param[command] SQL语句
     */
    @Deprecated("此方法不安全，会引起SQL注入攻击", ReplaceWith("selectObjects(command: String, vararg parameters: Any?)"))
    inline fun <reified T> selectObjectsUnsafe(command: String): ArrayList<T> = selectObjectsUnsafe(T::class.java, command)

    /**
     * 不安全的查询数据库，并以对象的形式返回数据
     *
     * @param[command] SQL语句
     *
     * @return 未查询到返回null，查询结果有多条则返回第一条
     */
    @Deprecated("此方法不安全，会引起SQL注入攻击", ReplaceWith("selectObject(command: String, vararg parameters: Any?)"))
    inline fun <reified T> selectObjectUnsafe(command: String): T? = selectObjectUnsafe(T::class.java, command)

    /**
     * 查询数据库，并以值集合的形式返回数据
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     */
    inline fun <reified T> selectValues(command: String, vararg parameters: Any?): ArrayList<T> = selectValues(T::class.java, command, *parameters)

    /**
     * 查询数据库，并以值的形式返回数据
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     *
     * @return 未查询到返回null，查询结果有多条则返回第一条
     */
    inline fun <reified T> selectValue(command: String, vararg parameters: Any?): T? = selectValue(T::class.java, command, *parameters)
    // endregion

    // region java中调用
    /**
     * Java中调用，查询数据库，并以对象集合的形式返回数据
     *
     * @param[targetClass] 转换的Java类
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     */
    fun <T> selectObjects(targetClass: Class<T>, command: String, vararg parameters: Any?): ArrayList<T> = if (setParameters(command, *parameters)) statement!!.executeQuery().selectObjects(targetClass) else ArrayList()

    /**
     * Java中调用，查询数据库，并以对象的形式返回数据
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     *
     * @return 未查询到返回null，查询结果有多条则返回第一条
     */
    fun <T> selectObject(targetClass: Class<T>, command: String, vararg parameters: Any?): T? = selectObjects(targetClass, command, *parameters).getOrNull(0)

    /**
     * 不安全的查询数据库，并以对象集合的形式返回数据
     *
     * @param[command] SQL语句
     */
    @Deprecated("此方法不安全，会引起SQL注入攻击", ReplaceWith("selectObjects(targetClass: Class<T>, command: String, vararg parameters: Any?)"))
    fun <T> selectObjectsUnsafe(targetClass: Class<T>, command: String): ArrayList<T> = connection!!.createStatement().executeQuery(command).selectObjects(targetClass)

    /**
     * 不安全的查询数据库，并以对象的形式返回数据
     *
     * @param[command] SQL语句
     *
     * @return 未查询到返回null，查询结果有多条则返回第一条
     */
    @Deprecated("此方法不安全，会引起SQL注入攻击", ReplaceWith("selectObject(targetClass: Class<T>, command: String, vararg parameters: Any?)"))
    inline fun <reified T> selectObjectUnsafe(targetClass: Class<T>, command: String): T? = selectObjectsUnsafe(targetClass, command).getOrNull(0)

    /**
     * Java中调用，查询数据库，并以值集合的形式返回数据
     *
     * @param[targetClass] 转换的Java类
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     */
    fun <T> selectValues(targetClass: Class<T>, command: String, vararg parameters: Any?): ArrayList<T> = if (setParameters(command, *parameters)) statement!!.executeQuery().selectValues(targetClass) else ArrayList()

    /**
     * Java中调用，查询数据库，并以值的形式返回数据
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     *
     * @return 未查询到返回null，查询结果有多条则返回第一条
     */
    fun <T> selectValue(targetClass: Class<T>, command: String, vararg parameters: Any?): T? = selectValues(targetClass, command, *parameters).getOrNull(0)

    /**
     * Java中调用，不安全查询数据库，并以值的形式返回数据
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     *
     * @return 未查询到返回null，查询结果有多条则返回第一条
     */
    @Deprecated("此方法不安全，会引起SQL注入攻击", ReplaceWith("selectValues(targetClass: Class<T>, command: String, vararg parameters: Any?)"))
    fun <T> selectValuesUnsafe(targetClass: Class<T>, command: String): ArrayList<T> = connection!!.createStatement().executeQuery(command).selectValues(targetClass)

    /**
     * Java中调用，不安全查询数据库，并以值的形式返回数据
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     *
     * @return 未查询到返回null，查询结果有多条则返回第一条
     */
    @Deprecated("此方法不安全，会引起SQL注入攻击", ReplaceWith("selectValue(targetClass: Class<T>, command: String, vararg parameters: Any?)"))
    fun <T> selectValueUnsafe(targetClass: Class<T>, command: String): T? = selectValuesUnsafe(targetClass, command).getOrNull(0)

    /**
     * 分页查询基类，不能直接调用，在具体实现类里调用
     *
     * @param[targetClass] 结果的强类型
     * @param[index] 页号，从0开始
     * @param[pageSize] 每页有多少数据
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[pageCommand] 查询总数据量的SQL语句
     * @param[parameters] 要替换占位符的参数
     *
     * @return 分页查询的结果，结果集合不会为null
     */
    fun <T> selectPageBase(targetClass: Class<T>, index: Int, pageSize: Int, command: String, pageCommand: String, vararg parameters: Any?): PageModel<T> {
        val result = PageModel<T>()
        result.index = index
        result.pageSize = pageSize
        result.count = selectValue(pageCommand, *parameters) ?: -1
        if (!targetClass.isPrimitiveType()) result.data.addAll(selectObjects(targetClass, command, *parameters))
        else result.data.addAll(selectValues(targetClass, command, *parameters))
        result.calc()
        return result
    }
    // endregion

    /**
     * 执行更新数据库操作，包括增、改和删
     *
     * @param[command] SQL语句，语句中参数占位符为 @p 或 @P 然后加数字，例如 @p0, @P1
     * @param[parameters] 要替换占位符的参数
     *
     * @return 更新的数据数目
     */
    fun update(command: String, vararg parameters: Any?): Int = if (setParameters(command, *parameters)) statement!!.executeUpdate() else 0

    /**
     * 不安全的执行更新数据库操作，包括增、改和删
     *
     * @param[command] SQL语句
     *
     * @return 更新的数据数目
     */
    @Deprecated("此方法不安全，会引起SQL注入攻击", ReplaceWith("update(command: String, vararg parameters: Any?)"))
    fun updateUnsafe(command: String): Int = if (isOpen()) connection!!.createStatement().executeUpdate(command) else 0

    /**
     * 获取生成的键
     */
    open fun getGeneratedKeys(): ArrayList<Any> {
        val generatedKeys = statement!!.generatedKeys
        val result = ArrayList<Any>(1)
        while (generatedKeys.next())
            for (i in 1..generatedKeys.metaData.columnCount)
                result.add(generatedKeys.getObject(i))
        return result
    }

}
